// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io' as io;

import 'package:observatory/service_io.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';

import 'test_helper.dart';

final dartJITBinary = path.join(path.dirname(io.Platform.resolvedExecutable),
    'dart' + path.extension(io.Platform.resolvedExecutable));

Future setupProcesses() async {
  final dir = await io.Directory.systemTemp.createTemp('file_service');

  final args = [
    ...io.Platform.executableArguments,
    '--pause_isolates_on_start',
    io.Platform.script.toFilePath(),
  ];
  io.Process? process1;
  io.Process? process2;
  io.Process? process3;

  void closeDown() {
    if (process1 != null) {
      process1!.kill();
    }
    if (process2 != null) {
      process2!.kill();
    }
    if (process3 != null) {
      process3!.kill();
    }
    dir.deleteSync(recursive: true);
  }

  Future<ServiceExtensionResponse> cleanup(ignored_a, ignored_b) {
    closeDown();
    final result = jsonEncode({'type': 'foobar'});
    return Future.value(ServiceExtensionResponse.result(result));
  }

  Future<ServiceExtensionResponse> setup(ignored_a, ignored_b) async {
    try {
      process1 = await io.Process.start(io.Platform.executable, args);
      process2 =
          await io.Process.start(io.Platform.executable, args..add('foobar'));
      final codeFilePath = dir.path + io.Platform.pathSeparator + "other_file";
      final codeFile = io.File(codeFilePath);
      await codeFile.writeAsString('''
          import "dart:io";

          void main() async {
            await stdin.drain();
          }
          ''');
      process3 = await io.Process.start(
          dartJITBinary, [...io.Platform.executableArguments, codeFilePath]);
    } catch (_) {
      closeDown();
      rethrow;
    }

    final result = jsonEncode({
      'type': 'foobar',
      'pids': [process1!.pid, process2!.pid, process3!.pid]
    });
    return Future.value(ServiceExtensionResponse.result(result));
  }

  Future<ServiceExtensionResponse> closeStdin(ignored_a, ignored_b) {
    process3!.stdin.close();
    return process3!.exitCode.then<ServiceExtensionResponse>((int exit) {
      final result = jsonEncode({'type': 'foobar'});
      return ServiceExtensionResponse.result(result);
    });
  }

  registerExtension('ext.dart.io.cleanup', cleanup);
  registerExtension('ext.dart.io.setup', setup);
  registerExtension('ext.dart.io.closeStdin', closeStdin);
}

final processTests = <IsolateTest>[
  // Initial.
  (Isolate isolate) async {
    final setup = await isolate.invokeRpcNoUpgrade('ext.dart.io.setup', {});
    try {
      var all = await isolate
          .invokeRpcNoUpgrade('ext.dart.io.getSpawnedProcesses', {});
      expect(all['type'], equals('SpawnedProcessList'));

      expect(all['processes'].length, equals(3));

      final first = await isolate.invokeRpcNoUpgrade(
          'ext.dart.io.getSpawnedProcessById',
          {'id': all['processes'][0]['id']});
      expect(first['name'], io.Platform.executable);
      expect(first['pid'], equals(setup['pids'][0]));
      expect(first['arguments'].contains('foobar'), isFalse);
      expect(first['startedAt'], greaterThan(0));

      final second = await isolate.invokeRpcNoUpgrade(
          'ext.dart.io.getSpawnedProcessById',
          {'id': all['processes'][1]['id']});
      expect(second['name'], io.Platform.executable);
      expect(second['pid'], equals(setup['pids'][1]));
      expect(second['arguments'].contains('foobar'), isTrue);
      expect(second['pid'] != first['pid'], isTrue);
      expect(second['startedAt'], greaterThan(0));
      expect(second['startedAt'], greaterThanOrEqualTo(first['startedAt']));

      final third = await isolate.invokeRpcNoUpgrade(
          'ext.dart.io.getSpawnedProcessById',
          {'id': all['processes'][2]['id']});
      expect(third['name'], dartJITBinary);
      expect(third['pid'], equals(setup['pids'][2]));
      expect(third['pid'] != first['pid'], isTrue);
      expect(third['pid'] != second['pid'], isTrue);
      expect(third['startedAt'], greaterThanOrEqualTo(second['startedAt']));

      await isolate.invokeRpcNoUpgrade('ext.dart.io.closeStdin', {});
      all = await isolate
          .invokeRpcNoUpgrade('ext.dart.io.getSpawnedProcesses', {});
      expect(all['type'], equals('SpawnedProcessList'));
      expect(all['processes'].length, equals(2));
    } finally {
      await isolate.invokeRpcNoUpgrade('ext.dart.io.cleanup', {});
    }
  },
];

main(args) async =>
    runIsolateTests(args, processTests, testeeBefore: setupProcesses);
